package iniconfig

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

func Marshal(data interface{}) (result []byte, err error) {
	typeInfo := reflect.TypeOf(data)
	valueInfo := reflect.ValueOf(data)
	resultTotal := ""
	if typeInfo.Kind() != reflect.Struct {
		err = errors.New("Please use struct")
		return
	}
	for i := 0; i < typeInfo.NumField(); i++ {
		sectionField := typeInfo.Field(i)
		if sectionField.Type.Kind() != reflect.Struct {
			err = errors.New("Please use struct")
			return
		}
		sectionValue := valueInfo.FieldByName(sectionField.Name)
		resultStr, errPase := parseFileSection(sectionField, sectionValue)
		if errPase != nil {
			err = errPase
			return
		}
		resultTotal += resultStr
		resultTotal += "\n\n"
	}
	result = []byte(resultTotal)
	return
}

func parseFileSection(sectionField reflect.StructField, sectionValue reflect.Value) (resultStr string, err error) {
	tagValue := sectionField.Tag.Get("ini")
	if len(tagValue) == 0 {
		err = fmt.Errorf("syntax error, invalid section:%s, tagValue:%s", sectionField.Name, tagValue)
		return
	}
	resultStr = fmt.Sprintf("[%s]", tagValue)
	sectionField.Type.NumField()
	for i := 0; i < sectionField.Type.NumField(); i++ {
		fieldString := ""
		field := sectionField.Type.Field(i)
		fieldValue := sectionValue.FieldByName(field.Name)
		fieldString = fmt.Sprintf("%v", fieldValue.Interface())
		// fieldType := field.Type
		// k := fieldType.Kind()
		// switch k {
		// case reflect.String:
		// 	fieldString = fieldValue.String()
		// case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		// 	fieldString = fmt.Sprintf("%d", fieldValue.Int())
		// case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		// 	fieldString = fmt.Sprintf("%d", fieldValue.Uint())
		// case reflect.Bool:
		// 	fieldString = fmt.Sprintf("%v", fieldValue.Bool())
		// case reflect.Float32, reflect.Float64:
		// 	fieldString = fmt.Sprintf("%f", fieldValue.Float())
		// default:
		// 	err = fmt.Errorf("syntax error, unsupport fieldName(%s) kind:%v", field.Name, k)
		// 	return
		// }
		if len(fieldString) > 0 {
			resultStr += "\n"
			resultStr += fmt.Sprintf("%s=%s", sectionField.Name, fieldString)
		}
	}
	return
}

func UnMarshal(data []byte, result interface{}) (err error) {
	lineArr := strings.Split(string(data), "\n")
	typeInfo := reflect.TypeOf(result)
	if typeInfo.Kind() != reflect.Ptr {
		err = errors.New("Please use address")
		return
	}
	resultElem := typeInfo.Elem()
	if resultElem.Kind() != reflect.Struct {
		err = errors.New("Please use struct")
		return
	}
	var lastSectionField string
	for index, line := range lineArr {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") || line == "" {
			continue
		}
		if len(line) <= 2 {
			err = fmt.Errorf("syntax error, invalid section:%s, lineno:%d", line, index+1)
			return
		}
		if strings.HasPrefix(line, "[") && !strings.HasSuffix(line, "]") {
			err = fmt.Errorf("syntax error, invalid section:%s, lineno:%d", line, index+1)
			return
		}
		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
			lastSectionField, err = parseSection(line, resultElem)
			if err != nil {
				err = fmt.Errorf("%v, lineno:%d", err, index+1)
				return
			}
			continue
		}
		if strings.Index(line, "=") == -1 {
			err = fmt.Errorf("syntax error, invalid item:%s, lineno:%d", line, index+1)
			return
		}
		err = parseItem(lastSectionField, line, result)
		if err != nil {
			err = fmt.Errorf("%v, lineno:%d", err, index+1)
			return
		}

	}

	return
}

func parseSection(line string, resultElem reflect.Type) (fieldName string, err error) {
	setctionName := strings.TrimSpace(string(line[1 : len(line)-1]))
	if setctionName == "" {
		err = fmt.Errorf("syntax error, invalid section:%s", line)
		return
	}
	for i := 0; i < resultElem.NumField(); i++ {
		field := resultElem.Field(i)
		tagValue := field.Tag.Get("ini")
		if tagValue == setctionName {
			fieldName = field.Name
			break
		}
	}
	return
}

func parseItem(sectionName string, line string, result interface{}) (err error) {
	index := strings.Index(line, "=")
	key := strings.TrimSpace(line[0:index])
	value := strings.TrimSpace(line[index+1:])
	if len(key) == 0 {
		err = fmt.Errorf("syntax error, invalid item:%s", line)
		return
	}
	resultReflectValue := reflect.ValueOf(result)
	section := resultReflectValue.Elem().FieldByName(sectionName)
	sectionType := section.Type()
	if section.Kind() != reflect.Struct {
		err = fmt.Errorf("syntax error, invalid result section:%s, please use struct", sectionName)
		return
	}
	var fieldName string
	for i := 0; i < sectionType.NumField(); i++ {
		field := sectionType.Field(i)
		tagValue := field.Tag.Get("ini")
		if tagValue == key {
			fieldName = field.Name
			fieldValue := section.FieldByName(fieldName)
			k := fieldValue.Kind()
			switch k {
			case reflect.String:
				fieldValue.SetString(value)
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				valueInt, err := strconv.ParseInt(value, 10, 64)
				if err != nil {
					err = fmt.Errorf("syntax error, unsupport fieldName(%s) kind:%v for item(%s)", fieldName, k, value)
					return err
				}
				fieldValue.SetInt(valueInt)
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				valueInt, err := strconv.ParseUint(value, 10, 64)
				if err != nil {
					err = fmt.Errorf("syntax error, unsupport fieldName(%s) kind:%v for item(%s)", fieldName, k, value)
					return err
				}
				fieldValue.SetUint(valueInt)
			case reflect.Bool:
				valueBool, err := strconv.ParseBool(value)
				if err != nil {
					err = fmt.Errorf("syntax error, unsupport fieldName(%s) kind:%v for item(%s)", fieldName, k, value)
					return err
				}
				fieldValue.SetBool(valueBool)
			case reflect.Float32, reflect.Float64:
				valueFloat, err := strconv.ParseFloat(value, 64)
				if err != nil {
					err = fmt.Errorf("syntax error, unsupport fieldName(%s) kind:%v for item(%s)", fieldName, k, value)
					return err
				}
				fieldValue.SetFloat(valueFloat)
			default:
				err = fmt.Errorf("syntax error, unsupport fieldName(%s) kind:%v", fieldName, k)
				return err
			}
		}
	}
	return
}
